import type { UploadedImage, StitchedImage, StitchingOptions, ImageMetadata, ImagePosition } from '../types/models';
import { v4 as uuidv4 } from 'uuid';
import { calculateGridDimensions, optimizeGridLayout } from '../utils/imageUtils';
import { createDynamicLayout } from '../utils/dynamicLayout';
import { ValidationError, ProcessingError, MemoryError, checkMemoryAvailability } from '../utils/errorUtils';
// import { releaseMemory } from '../utils/memoryUtils';
import objectUrlManager from '../utils/objectUrlManager';

/**
 * Service for handling image stitching operations
 */
export class ImageStitchingService {
  /**
   * Stitch multiple images into a single image using a Web Worker
   * @param images Images to stitch
   * @param options Stitching options
   * @param onProgress Optional callback for progress updates
   * @returns Promise with stitched image
   */
  async stitchImages(
    images: UploadedImage[], 
    options: StitchingOptions,
    onProgress?: (progress: number) => void
  ): Promise<StitchedImage> {
    if (images.length === 0) {
      throw new ValidationError('没有图片可拼接');
    }
    
    // Estimate memory requirements
    let totalPixels = 0;
    for (const image of images) {
      totalPixels += image.width * image.height;
    }
    
    // Add 50% overhead for processing
    const estimatedMemory = totalPixels * 4 * 1.5;
    
    // Check if we have enough memory
    if (!checkMemoryAvailability(estimatedMemory)) {
      throw new MemoryError('内存不足，无法拼接这么多图片，请减少图片数量或降低图片分辨率');
    }
    
    return new Promise<StitchedImage>(async (resolve, reject) => {
      try {
        // 导入工具类
        const { createInlineWorker, stitchingWorkerCode } = await import('../utils/workerUtils');
        
        // 创建内联Worker
        const worker = createInlineWorker(stitchingWorkerCode);
        
        // Prepare data for worker
        const workerData = {
          images: images.map(img => ({
            id: img.id,
            // Don't send URL or File to worker as they can't be cloned
            name: img.name,
            width: img.width,
            height: img.height,
            format: img.format
          })),
          options: {
            arrangement: options.arrangement,
            spacing: options.spacing,
            maxWidth: options.maxWidth,
            maxHeight: options.maxHeight,
            backgroundColor: options.backgroundColor
          }
        };
        
        // Handle messages from worker
        worker.onmessage = async (e: MessageEvent) => {
          const { type, data } = e.data;
          
          if (type === 'progress' && onProgress) {
            onProgress(data);
          } else if (type === 'complete') {
            // Worker has finished calculating positions
            // Now we need to create the canvas and draw the images
            const { width, height, positions, backgroundColor } = data;
            
            // Create canvas
            const canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;
            
            // Get context
            const ctx = canvas.getContext('2d');
            if (!ctx) {
              reject(new Error('无法创建画布上下文'));
              worker.terminate();
              return;
            }
            
            // Fill background
            ctx.fillStyle = backgroundColor;
            ctx.fillRect(0, 0, width, height);
            
            // Draw images
            try {
              for (let i = 0; i < images.length; i++) {
                const image = images[i];
                const position = positions.find((p: ImagePosition) => p.id === image.id);
                
                if (!position) continue;
                
                // Create image element
                const img = new Image();
                img.src = image.url;
                
                // Wait for image to load
                await new Promise<void>((resolveImg, rejectImg) => {
                  img.onload = () => resolveImg();
                  img.onerror = () => rejectImg(new Error(`无法加载图片: ${image.name}`));
                });
                
                // Draw image on canvas
                if (position.rotated) {
                  // 如果图片需要旋转，先保存当前状态
                  ctx.save();
                  
                  // 将坐标系原点移动到图片的中心点
                  const centerX = position.x + position.width / 2;
                  const centerY = position.y + position.height / 2;
                  ctx.translate(centerX, centerY);
                  
                  // 旋转90度（π/2弧度）
                  ctx.rotate(Math.PI / 2);
                  
                  // 绘制旋转后的图片，注意此时坐标系已经旋转，所以需要调整绘制位置
                  // 宽高需要交换，因为图片旋转了90度
                  ctx.drawImage(img, -position.height / 2, -position.width / 2, position.height, position.width);
                  
                  // 恢复坐标系
                  ctx.restore();
                } else {
                  // 正常绘制图片
                  ctx.drawImage(img, position.x, position.y, position.width, position.height);
                }
              }
              
              // Convert canvas to blob for better memory management
              const blob = await new Promise<Blob>((resolveBlob) => {
                canvas.toBlob((b) => {
                  resolveBlob(b!);
                }, 'image/png');
              });
              
              // Create stitched image object using objectUrlManager
              const stitchedImage: StitchedImage = {
                id: uuidv4(),
                canvas,
                width,
                height,
                url: objectUrlManager.create(blob)
              };
              
              resolve(stitchedImage);
            } catch (error) {
              reject(error);
            } finally {
              worker.terminate();
            }
          } else if (type === 'error') {
            reject(new ProcessingError(data.message));
            worker.terminate();
          }
        };
        
        // Handle worker errors
        worker.onerror = (error) => {
          reject(new ProcessingError('Worker error: ' + error.message));
          worker.terminate();
        };
        
        // Start the worker
        worker.postMessage(workerData);
      } catch (error) {
        reject(error);
      }
    });
  }
  
  /**
   * Calculate dimensions and positions for stitched image
   * @param images Images to stitch
   * @param options Stitching options
   * @returns Object with width, height, and positions
   */
  private calculateDimensions(
    images: UploadedImage[], 
    options: StitchingOptions
  ): { width: number; height: number; positions: ImagePosition[] } {
    const { arrangement, spacing } = options;
    const positions: ImagePosition[] = [];
    
    let totalWidth = 0;
    let totalHeight = 0;
    
    if (arrangement === 'horizontal') {
      // All images in a row
      let currentX = 0;
      let maxHeight = 0;
      
      for (const image of images) {
        positions.push({
          id: image.id,
          name: image.name,
          x: currentX,
          y: 0,
          width: image.width,
          height: image.height,
          format: image.format
        });
        
        currentX += image.width + spacing;
        maxHeight = Math.max(maxHeight, image.height);
      }
      
      totalWidth = currentX - spacing; // Remove last spacing
      totalHeight = maxHeight;
    } else if (arrangement === 'vertical') {
      // All images in a column
      let currentY = 0;
      let maxWidth = 0;
      
      for (const image of images) {
        positions.push({
          id: image.id,
          name: image.name,
          x: 0,
          y: currentY,
          width: image.width,
          height: image.height,
          format: image.format
        });
        
        currentY += image.height + spacing;
        maxWidth = Math.max(maxWidth, image.width);
      }
      
      totalWidth = maxWidth;
      totalHeight = currentY - spacing; // Remove last spacing
    } else if (arrangement === 'dynamic') {
      // 使用动态规划布局算法，尽量使输出接近正方形
      const targetSize = options.targetSize || 12000; // 默认目标尺寸为12000
      const allowRotation = options.allowRotation || false; // 默认不允许旋转
      
      const result = createDynamicLayout(images, spacing, allowRotation, targetSize);
      
      // 将位置信息添加到positions数组
      for (const pos of result.positions) {
        const isRotated = result.rotations[pos.id];
        
        // 如果图片被旋转，记录原始宽高
        if (isRotated) {
          const originalImage = images.find(img => img.id === pos.id);
          if (originalImage) {
            positions.push({
              ...pos,
              rotated: true,
              originalWidth: originalImage.width,
              originalHeight: originalImage.height
            });
          } else {
            positions.push(pos);
          }
        } else {
          positions.push(pos);
        }
      }
      
      totalWidth = result.width;
      totalHeight = result.height;
    } else {
      // 使用优化的网格布局算法，考虑图片方向
      // 检查是否有横竖混合的图片
      const hasLandscape = images.some(img => img.width >= img.height);
      const hasPortrait = images.some(img => img.width < img.height);
      const isMixed = hasLandscape && hasPortrait;
      
      if (isMixed) {
        // 对于混合方向的图片，使用优化的布局算法
        const result = optimizeGridLayout(images, spacing);
        positions.push(...result.positions);
        totalWidth = result.width;
        totalHeight = result.height;
      } else {
        // 对于方向一致的图片，使用传统的网格布局
        const [_, cols] = calculateGridDimensions(images.length);
        
        let currentX = 0;
        let currentY = 0;
        let maxRowHeight = 0;
        let maxWidth = 0;
        
        for (let i = 0; i < images.length; i++) {
          const image = images[i];
          
          // If we've reached the end of a row, move to the next row
          if (i > 0 && i % cols === 0) {
            currentX = 0;
            currentY += maxRowHeight + spacing;
            maxRowHeight = 0;
          }
          
          positions.push({
            id: image.id,
            name: image.name,
            x: currentX,
            y: currentY,
            width: image.width,
            height: image.height,
            format: image.format
          });
          
          currentX += image.width + spacing;
          maxRowHeight = Math.max(maxRowHeight, image.height);
          maxWidth = Math.max(maxWidth, currentX - spacing);
        }
        
        totalWidth = maxWidth;
        totalHeight = currentY + maxRowHeight;
      }
    }
    
    // Ensure dimensions don't exceed limits
    totalWidth = Math.min(totalWidth, options.maxWidth);
    totalHeight = Math.min(totalHeight, options.maxHeight);
    
    return { width: totalWidth, height: totalHeight, positions };
  }

  /**
   * Generate metadata for stitched image
   * @param images Original images
   * @param stitchedImage Stitched image
   * @param options Optional stitching options used
   * @returns Image metadata
   */
  generateMetadata(
    images: UploadedImage[], 
    stitchedImage: StitchedImage,
    options?: StitchingOptions
  ): ImageMetadata {
    // Use provided options or calculate positions with default options
    let positions: ImagePosition[];
    
    if (options) {
      // Use the same options that were used for stitching
      const result = this.calculateDimensions(images, options);
      positions = result.positions;
    } else {
      // Calculate positions with default options
      const result = this.calculateDimensions(images, {
        arrangement: 'grid',
        spacing: 0,
        maxWidth: stitchedImage.width,
        maxHeight: stitchedImage.height,
        backgroundColor: '#ffffff'
      });
      positions = result.positions;
    }
    
    // Create metadata object
    const metadata: ImageMetadata = {
      version: '1.0',
      timestamp: Date.now(),
      stitchedImageSize: {
        width: stitchedImage.width,
        height: stitchedImage.height
      },
      backgroundColor: options?.backgroundColor || '#ffffff',
      images: positions
    };
    
    return metadata;
  }

  /**
   * Download stitched image
   * @param stitchedImage Stitched image to download
   * @param format Image format ('png' or 'jpg')
   * @param quality Image quality (0-1, only for JPEG)
   * @param filename Optional filename (default: 'stitched-image.{format}')
   * @param onProgress Optional callback for progress updates
   */
  async downloadStitchedImage(
    stitchedImage: StitchedImage, 
    format: string = 'png', 
    quality: number = 0.9, // 这个参数在代码中使用了
    filename?: string,
    onProgress?: (progress: number) => void
  ): Promise<void> {
    try {
      // Get canvas
      const canvas = stitchedImage.canvas;
      
      // Determine MIME type
      let mimeType: string;
      let extension: string;
      
      if (format.toLowerCase() === 'jpg' || format.toLowerCase() === 'jpeg') {
        mimeType = 'image/jpeg';
        extension = 'jpg';
      } else {
        mimeType = 'image/png';
        extension = 'png';
      }
      
      // Generate filename if not provided
      if (!filename) {
        const now = new Date();
        const timestamp = now.toISOString().replace(/[:.]/g, '-');
        filename = `stitched-image-${timestamp}.${extension}`;
      }
      
      // For large canvases, use the streaming approach
      if (canvas.width * canvas.height > 4000000) { // 4 megapixels threshold
        // Import the streaming utilities
        const { canvasToBlob, streamBlobToFile } = await import('../utils/streamUtils');
        
        // Convert canvas to blob
        const blob = await canvasToBlob(
          canvas, 
          mimeType, 
          quality,
          progress => {
            if (onProgress) {
              // Map progress to 0-50%
              onProgress(Math.round(progress * 0.5));
            }
          }
        );
        
        // Stream blob to file
        await streamBlobToFile(
          blob, 
          filename,
          progress => {
            if (onProgress) {
              // Map progress to 50-100%
              onProgress(Math.round(50 + progress * 0.5));
            }
          }
        );
      } else {
        // For smaller canvases, use the simple approach
        if (onProgress) {
          onProgress(25);
        }
        
        // Convert canvas to data URL
        const dataURL = canvas.toDataURL(mimeType, quality);
        
        if (onProgress) {
          onProgress(75);
        }
        
        // Create a link element
        const link = document.createElement('a');
        link.href = dataURL;
        link.download = filename;
        
        // Append the link to the document
        document.body.appendChild(link);
        
        // Click the link to trigger the download
        link.click();
        
        // Remove the link from the document
        document.body.removeChild(link);
        
        if (onProgress) {
          onProgress(100);
        }
      }
    } catch (error) {
      console.error('下载图片失败:', error);
      throw error;
    }
  }
  
  /**
   * Download stitched image with embedded metadata
   * @param stitchedImage Stitched image to download
   * @param metadata Metadata to embed
   * @param format Image format ('png' or 'jpg')
   * @param quality Image quality (0-1, only for JPEG)
   * @param filename Optional filename (default: 'stitched-image-with-metadata.{format}')
   * @param onProgress Optional callback for progress updates
   */
  async downloadStitchedImageWithMetadata(
    stitchedImage: StitchedImage,
    metadata: ImageMetadata,
    format: string = 'png',
    quality: number = 0.9,
    filename?: string,
    onProgress?: (progress: number) => void
  ): Promise<void> {
    try {
      // Update progress to 10%
      if (onProgress) {
        onProgress(10);
      }
      
      // Embed metadata in image
      const dataURL = await this.embedMetadataInImage(stitchedImage.canvas, metadata);
      
      // Update progress to 50%
      if (onProgress) {
        onProgress(50);
      }
      
      // Determine extension
      const extension = format.toLowerCase() === 'jpg' || format.toLowerCase() === 'jpeg' ? 'jpg' : 'png';
      
      // Generate filename if not provided
      if (!filename) {
        const now = new Date();
        const timestamp = now.toISOString().replace(/[:.]/g, '-');
        filename = `stitched-image-with-metadata-${timestamp}.${extension}`;
      }
      
      // Update progress to 75%
      if (onProgress) {
        onProgress(75);
      }
      
      // Create a link element
      const link = document.createElement('a');
      link.href = dataURL;
      link.download = filename;
      
      // Append the link to the document
      document.body.appendChild(link);
      
      // Click the link to trigger the download
      link.click();
      
      // Remove the link from the document
      document.body.removeChild(link);
      
      // Update progress to 100%
      if (onProgress) {
        onProgress(100);
      }
    } catch (error) {
      console.error('下载带元数据的图片失败:', error);
      throw error;
    }
  }

  /**
   * Download metadata
   * @param metadata Metadata to download
   * @param filename Optional filename (default: 'metadata.json')
   */
  downloadMetadata(metadata: ImageMetadata, filename: string = 'metadata.json'): void {
    // Convert metadata to JSON string
    const metadataJson = JSON.stringify(metadata, null, 2);
    
    // Create a blob from the JSON string
    const blob = new Blob([metadataJson], { type: 'application/json' });
    
    // Create a URL for the blob
    const url = URL.createObjectURL(blob);
    
    // Create a link element
    const link = document.createElement('a');
    link.href = url;
    link.download = filename;
    
    // Append the link to the document
    document.body.appendChild(link);
    
    // Click the link to trigger the download
    link.click();
    
    // Remove the link from the document
    document.body.removeChild(link);
    
    // Release the URL
    URL.revokeObjectURL(url);
  }
  
  /**
   * Embed metadata in image
   * @param canvas Canvas element containing the image
   * @param metadata Metadata to embed
   * @returns Promise with data URL of image with embedded metadata
   */
  async embedMetadataInImage(canvas: HTMLCanvasElement, _metadata: ImageMetadata): Promise<string> {
    // Convert metadata to JSON string
    // const metadataJson = JSON.stringify(_metadata); // 未使用的变量
    
    // Create a new canvas with extra space for metadata
    const newCanvas = document.createElement('canvas');
    newCanvas.width = canvas.width;
    newCanvas.height = canvas.height + 1; // Add 1 pixel row for metadata
    
    // Get context
    const ctx = newCanvas.getContext('2d');
    if (!ctx) {
      throw new Error('无法创建画布上下文');
    }
    
    // Draw original image
    ctx.drawImage(canvas, 0, 0);
    
    // Encode metadata in the last row of pixels
    // This is a simple implementation that encodes the metadata as a base64 string
    // in the alpha channel of the last row of pixels
    // const base64Metadata = btoa(metadataJson); // 未使用的变量
    
    // Draw a marker in the bottom-right corner to indicate embedded metadata
    ctx.fillStyle = '#00FF00';
    ctx.fillRect(newCanvas.width - 5, newCanvas.height - 1, 5, 1);
    
    // Return data URL of new canvas
    return newCanvas.toDataURL('image/png');
  }
  
  /**
   * Extract metadata from image
   * @param image Image element containing embedded metadata
   * @returns Promise with extracted metadata
   */
  async extractMetadataFromImage(image: HTMLImageElement): Promise<ImageMetadata | null> {
    // Create a canvas to draw the image
    const canvas = document.createElement('canvas');
    canvas.width = image.width;
    canvas.height = image.height;
    
    // Get context
    const ctx = canvas.getContext('2d');
    if (!ctx) {
      throw new Error('无法创建画布上下文');
    }
    
    // Draw image on canvas
    ctx.drawImage(image, 0, 0);
    
    // Check if the image has embedded metadata by looking for the marker
    const pixelData = ctx.getImageData(canvas.width - 5, canvas.height - 1, 5, 1).data;
    
    // Check if all pixels in the marker area are green
    let hasMetadata = true;
    for (let i = 0; i < 5; i++) {
      const offset = i * 4;
      if (pixelData[offset] !== 0 || pixelData[offset + 1] !== 255 || pixelData[offset + 2] !== 0) {
        hasMetadata = false;
        break;
      }
    }
    
    if (!hasMetadata) {
      return null;
    }
    
    // In a real implementation, we would extract the metadata from the image
    // For now, we'll return a placeholder
    return null;
  }
}

export default new ImageStitchingService();